前面實作 Speaker 及 Stringer 介面時所作,就是所謂 duck typing
duck typing 是程式設計中的一種推理 : 只要一個東西長得像鴨子、游泳像鴨子、叫聲也像鴨子,那麼它就是鴨子 !
Go 語言的duck typing,是根據型別方法來判斷型別符合介面,不是明確的指定哪些型別能夠符合。
package main
import "fmt"
type Speaker interface {
Speak() string
}
type cat struct {
}
func (c cat) Speak() string{
return "Purrrrr Meow"
}
func chatter(s Speaker) { //接收speaker介面型別的引數
fmt.Println(s.Speak())
}
func main() {
c := cat{}
chatter(c)
}
這次我們有一個函式 chatter(),它接收的參數型別是Speaker介面。既然cat結構隱性實作Speaker介面,因此他透過duck typing 被視為 Speaker 型別,可以傳入chatter()的參數。
duck typing 不只限於單一型別,接下來來看這個原理如何套用在多型。
Polymorphism(多型),指一樣東西可以有多種形式呈現,簡單的例子 :
一個形狀可以有圓形、正方形、矩形或其他形狀。
在物件導向語言中,子類別(subclassing)的意思指的是讓一個類別去繼承父類別屬性和方法,並允許進一步定義或重寫它們。
Go 語言,雖然不是一個純粹的物件導向語言,它沒有類別(class),但它提供了內嵌結構和介面這兩個機制,這使得開發者能夠模仿物件導向語言中的某些功能。
在Go語言中使用多型的好處之一 :
如果只讓該函式接收介面型別參數,那麼任何符合介面規範的型別都可以傳入,甚至不需要在函式中撰寫額外程式碼去應付每一種型別。
進階例子-如何在Go中運用多型 :
前面提過,任何實質型別可以實作多個介面; 相反的,一個介面也可以被多個型別實作。
Speaker interface 中有 Speak() 方法,而 cat、dog、person 都實作 Speaker 介面,這代表他們一定都有Speak()方法,而且會回傳一個字串。
這表示我們可以寫一個共用函式接收Speaker介面型別的參數,然後對任何傳入的值呼叫相同行為 :
package main
import "fmt"
type Speaker interface {
Speak() string
}
type cat struct { //cat結構
}
type dog string // dog 自訂型別
type person struct { // person結構
name string
}
func (c cat) Speak() string {
return "Purrrrr Meow"
}
func (d dog) Speak() string {
return "woof woof"
}
func (p person) Speak() string {
return p.name + ",誇爪kill,AAAAAAA"
}
func thingSpeak(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
c := cat{}
d := dog("")
p := person{name: "Heather"}
thingSpeak(c)
thingSpeak(d)
thingSpeak(p)
}
輸出結果 :
可以看到函式 thingSpeak() 能夠接收 cat、dog、person 型別的值,並呼叫他們的Speak()值,
以下作出一點變化,將thingSpeak()方法改為可接收不定參數 :
func thingSpeak(speakers ...Speaker) {
for _, s := range speakers {
fmt.Println(s.Speak())
}
}
thingSpeak()會去走訪 speakers (一個Speaker型別切片),並輪流呼叫每個元素Speak()方法。
接著修改main()中 :
thingSpeak(c, d, p)
完整程式 :
package main
import "fmt"
type Speaker interface {
Speak() string
}
type cat struct { //cat結構
}
type dog string // dog 自訂型別
type person struct { // person結構
name string
}
func (c cat) Speak() string {
return "Purrrrr Meow"
}
func (d dog) Speak() string {
return "woof woof"
}
func (p person) Speak() string {
return p.name + ",誇爪kill,AAAAAAA"
}
func thingSpeak(speakers ...Speaker) {
for _, s := range speakers {
fmt.Println(s.Speak())
}
}
func main() {
c := cat{}
d := dog("")
p := person{name: "巨鎚瑞斯"}
thingSpeak(c, d, p)
}
輸出結果也是一樣的,但是瞧瞧main()中,我們程式更加簡潔了!!!
以上就是duck typing與多型,接下來將作一個練習,熟悉一下。
寫一段程式
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Name() string
}
type circle struct {
radius float64
}
type square struct {
side float64
}
type triangle struct {
base float64
height float64
}
func printSharpDetails(shapes ...Shape) {
for _, item := range shapes {
fmt.Printf("%s的面積: %.2f\n", item.Name(), item.Area())
}
}
//實作面積
func (c circle) Area() float64 {
return c.radius * c.radius * math.Pi
}
func (c circle) Name() string {
return "圓形"
}
func (s square) Area() float64 {
return s.side * s.side
}
func (s square) Name() string {
return "正方形"
}
func (t triangle) Area() float64 {
return (t.base * t.height ) / 2
}
func (t triangle) Name() string {
return "三角形"
}
func main() {
s := square{side:100}
c := circle{radius: 6.5}
t := triangle{base: 15.8, height: 22.3}
printSharpDetails(s, c, t)
}
輸出結果 :
每一種形狀(circle、square、triangle)都滿足 Shape 介面,因為都具備 Area() 和 Name() 這兩種方法,且特徵也吻合。因此儘管每個結構的欄位有所不同,他們都可以被 printShapeDetails()函式使用。